package com.csthink.common.application.advice;

import com.csthink.common.application.ResponseWrapper;
import com.csthink.common.infrastructure.enums.ErrorEnum;
import com.csthink.common.infrastructure.exception.BaseException;
import com.csthink.common.infrastructure.util.HttpContextUtil;
import com.csthink.common.infrastructure.util.IpUtil;
import com.csthink.common.infrastructure.util.Jackson2Util;
import com.csthink.common.infrastructure.util.SpringUtil;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.*;

/**
 * 全局异常处理类
 *
 * @author <a href="mailto:security.2009@live.cn">Mars</a>
 * @since 2023-09-09
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {

    @Setter(onMethod_ = @Autowired)
    private SpringUtil springUtil;

    @Setter(onMethod_ = @Autowired)
    private MessageSource messageSource;

    @ExceptionHandler(value = BaseException.class)
    public ResponseWrapper<String> baseExceptionHandler(BaseException be) {
        ResponseWrapper<String> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode("" + be.getErrCode());
        response.setErrorMessage(getMessage(be.getErrorEnum(), be.getErrorMsg()));
        buildErrorResult(response, be.getMessage());
        log.error("BaseException Handler", be);
        return response;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public ResponseWrapper<String> handleBindExceptions(BindException ex) {
//        List<String> errors = new ArrayList<>();
//        ex.getBindingResult().getAllErrors().forEach(error -> {
//            String fieldName = ((FieldError) error).getField();
//            errors.add(fieldName);
//        });

        ResponseWrapper<String> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode("" + HttpStatus.BAD_REQUEST.value());
        response.setErrorMessage("Bad request: " + HttpStatus.BAD_REQUEST.getReasonPhrase());
        buildErrorResult(response, ex.getMessage());
        log.error("Bad request: BindException Handler. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
        return response;
    }

    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseWrapper methodNotSupportException(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) {
        ResponseWrapper response = new ResponseWrapper();
        response.setStatus(false);
        response.setErrorCode("" + HttpStatus.METHOD_NOT_ALLOWED.value());
        response.setErrorMessage("Bad request: " + HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase());
        buildErrorResult(response, ex.getMessage());
        log.error("Bad request: HttpRequestMethodNotSupportedException. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
        return response;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseWrapper<String> handleMissingServletRequestParameterExceptions(MissingServletRequestParameterException ex) {
        ResponseWrapper<String> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode("" + HttpStatus.BAD_REQUEST.value());
        response.setErrorMessage("Bad request: " + HttpStatus.BAD_REQUEST.getReasonPhrase());
        buildErrorResult(response, ex.getMessage());
        log.error("Bad request: MissingServletRequestParameterException Handler. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
        return response;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseWrapper<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            if (error instanceof FieldError) {
                String fieldName = ((FieldError) error).getField();
                String errorMessage = error.getDefaultMessage();
                errors.put(fieldName, errorMessage);
            } else {
                errors.put(error.getObjectName(), error.getDefaultMessage());
            }
        });

        ResponseWrapper<Map<String, String>> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode("" + HttpStatus.BAD_REQUEST.value());
        response.setErrorMessage("Bad request: " + HttpStatus.BAD_REQUEST.getReasonPhrase());
        buildErrorResult(response, errors);
        log.error("Bad request: MethodArgumentNotValidException Handler. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
        return response;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
    public ResponseWrapper<String> handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException ex) {
        ResponseWrapper<String> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode("" + HttpStatus.BAD_REQUEST.value());
        response.setErrorMessage("Bad request: " + HttpStatus.BAD_REQUEST.getReasonPhrase());
        buildErrorResult(response, ex.getMessage());
        log.error("Bad request: HttpMediaTypeNotAcceptableException Handler. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
        justPrintInDev(ex.getClass().getName());
        return response;
    }

    /**
     * 处理 Tomcat 链接异常
     *
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_GATEWAY)
    @ExceptionHandler(value = ClientAbortException.class)
    public ResponseWrapper<String> handleClientAbortException(ClientAbortException ex) {
        ResponseWrapper<String> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode(String.valueOf(ErrorEnum.INTERNAL_SERVER_ERROR.getErrorCode()));
        response.setErrorMessage(messageSource.getMessage("-1", null, LocaleContextHolder.getLocale()));
        buildErrorResult(response, ex.getMessage());

        Throwable cause = ex.getCause();
        if (cause != null && cause instanceof IOException && cause.getMessage().contains("Broken pipe")) {
            log.warn("ClientAbortException Handler, IOBroken pipe. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
            response.setErrorMessage("Broken pipe");
        } else {
            log.error("ClientAbortException Handler. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
        }

        return response;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseWrapper<String> handleValidationExceptions(ConstraintViolationException ex) {
        ResponseWrapper<String> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode("" + HttpStatus.BAD_REQUEST.value());
        response.setErrorMessage("Bad request: " + HttpStatus.BAD_REQUEST.getReasonPhrase());
        buildErrorResult(response, ex.getMessage());
        log.error("Bad request: ConstraintViolationException Handler. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
        return response;
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(code = HttpStatus.NOT_FOUND)
    public ResponseWrapper<String> handlerNoHandlerFoundException(NoHandlerFoundException ex) {
        ResponseWrapper<String> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode("" + HttpStatus.NOT_FOUND.value());
        response.setErrorMessage("Bad request: " + HttpStatus.NOT_FOUND.getReasonPhrase());
        buildErrorResult(response, ex.getMessage());
        log.error("Not found: NoHandlerFoundException Handler. Request URL: {}, error: ", getRequestMethodAndUri(), ex);
        return response;
    }

    /**
     * 处理其他的兜底异常
     *
     * @param ex
     * @return
     */
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    public ResponseWrapper<String> exceptionHandler(Exception ex) {
        ResponseWrapper<String> response = new ResponseWrapper<>();
        response.setStatus(false);
        response.setErrorCode(String.valueOf(ErrorEnum.INTERNAL_SERVER_ERROR.getErrorCode()));
        response.setErrorMessage(messageSource.getMessage("-1", null, LocaleContextHolder.getLocale()));
        buildErrorResult(response, ex.getMessage());
        log.error("Exception Handler", ex);
        return response;
    }

    private void buildErrorResult(ResponseWrapper responseWrapper, Object errors) {
        if (springUtil.isDevEnv()) {
            responseWrapper.setResult(errors);
        } else {
            responseWrapper.setResult(messageSource.getMessage("-1", null, LocaleContextHolder.getLocale()));
        }
    }

    private String getMessage(ErrorEnum errorEnum, String defaultMessage) {
        try {
            return messageSource.getMessage(String.valueOf(errorEnum.getErrorCode()), null, StringUtils.defaultString(defaultMessage, errorEnum.name()), LocaleContextHolder.getLocale());
        } catch (NoSuchMessageException e) {
            log.error("No such message source: {}", errorEnum);
            return "";
        }
    }

    private String getRequestMethodAndUri() {
        HttpServletRequest request = HttpContextUtil.getRequest();
        if (request != null && StringUtils.isNotBlank(request.getRequestURI()) && StringUtils.isNotBlank(request.getMethod())) {
            return request.getMethod() + " - " + request.getRequestURI();
        }

        return "not a http request";
    }

    private void justPrintInDev(String exceptionClassName) {
        if (springUtil.isDevEnv()) {
            String requestIp = null;
            Map<String, String> requestHeaders = new HashMap<>();

            // http request
            HttpServletRequest request = HttpContextUtil.getRequest();
            if (request != null) {
                // request IP
                requestIp = IpUtil.getIPAddress(request);

                // request headers
                Enumeration<String> headerNames = request.getHeaderNames();
                while (headerNames.hasMoreElements()) {
                    String key = headerNames.nextElement();
                    String value = request.getHeader(key);
                    requestHeaders.put(key, value);
                }
            }

            log.error("exceptionClassName: {},\n" +
                            "request IP: {},\n" +
                            "request headers: {},\n",
                    exceptionClassName,
                    requestIp,
                    Jackson2Util.toJsonIgnoreBlank(requestHeaders));
        }
    }
}
